import paramiko
import os
import sys
import subprocess
import json
import time
import shutil
import configparser

"""
pip install paramiko
pip install futures
pip install scp
pip install ruamel.yaml
"""

from pip._vendor import requests
from ruamel.yaml import YAML
from scp import SCPClient
from concurrent import futures

deploy_path = "./deploy-nodes"


def read_accounts():
    with open("./account.json", "rb") as infile:
        accounts = json.load(infile)
        return accounts


def connect(node):
    ssh = paramiko.SSHClient()
    ssh.load_system_host_keys()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(node["host"], node["port"], node["user"], node["passwd"])
    return ssh


def exec_cmd(ssh, cmd, passwd=None):
    try:
        print(cmd)
        stdin, stdout, stderr = ssh.exec_command(cmd)
        if passwd:
            stdin.write(passwd + "\n")
        for line in stdout.readlines():
            print(line)
    except:
        print("exec cmd %s error" % cmd)
        return


def exec_cmd_return(ssh, cmd, passwd=None):
    try:
        stdin, stdout, stderr = ssh.exec_command(cmd)
        if passwd:
            stdin.write(passwd + "\n")
        lines = []
        for line in stdout.readlines():
            lines.append(line)
        for line in stderr.readlines():
            lines.append(line)
        return lines
    except:
        print("exec {} expect".format(cmd))
        return []


def upload(ssh, file, dst):
    sftp = paramiko.SFTPClient.from_transport(ssh.get_transport())
    sftp = ssh.open_sftp()

    sftp.put(file, dst)
    sftp.close()


def check_eos_deb(nodes):
    for node in nodes:
        ssh = connect(node)
        lines = exec_cmd_return(ssh, "whereis nodeos | grep usr")
        print(len(lines))
        if len(lines) == 0:
            upload(ssh, "./eos/eosio_2.0.6-1-ubuntu-18.04_amd64.deb", "/tmp/eosio_2.0.6-1-ubuntu-18.04_amd64.deb")
            lines = exec_cmd_return(ssh, "sudo -S -p '' apt install /tmp/eosio_2.0.6-1-ubuntu-18.04_amd64.deb", node["passwd"])
            print(lines)
            lines = exec_cmd_return(ssh, "whereis nodeos | grep usr")
            if len(lines) != 0:
                info = "%s:%s install success" %(node["host"], node["port"])
                print(info.encode("utf-8"))

def deploy(config_file):
    def clear_dirty_nodes():
        cmd = "rm -rf " + deploy_path + "/*"
        subprocess.check_output([cmd], shell=True)

    clear_dirty_nodes()
    with open(config_file, "rb") as infile:
        nodes = json.load(infile)
        print("check nodeos")
        check_eos_deb(nodes)
        create_node_configs(nodes)
        init_boot(nodes[0], nodes)
        print("tar nodes data")
        tar_nodes(nodes)
        print("send tar file to server")
        send_nodes(nodes)
        print("start all nodes")
        start_all_nodes(nodes)
        status_all_nodes(nodes)


def start_producers():
    result = subprocess.check_output(["cleos set contract eosio ./eos/system/contracts/eosio.bios/"], shell=True)
    print(result)
    with open("./config.json", "rb") as infile:
        nodes = json.load(infile)
        add_producer(nodes)


def add_producer(nodes):
    time.sleep(1)
    node_list = ""
    for node in nodes:
        node_list += ",{\"producer_name\":\"%s\",\"block_signing_key\":\"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"}" % (
            node["producer_name"])

    cmd = 'cleos push action eosio setprods \'{"schedule":[' + node_list[1:] + ']}\' -p eosio'
    print(cmd.encode("utf-8"))
    result = subprocess.check_output([cmd], shell=True)
    print(result)
    time.sleep(2)


def start_all_nodes(nodes):
    for node in nodes:
        ssh = connect(node)
        exec_cmd_return(ssh, "cd " + node["path"] + "/" + node["producer_name"] + " && ./start.sh")


def start_all_nodes_args(nodes, args):
    for node in nodes:
        ssh = connect(node)
        exec_cmd_return(ssh, "cd " + node["path"] + "/" + node["producer_name"] + " && ./start.sh " + args)


def stop_all_nodes(nodes):
    for node in nodes:
        ssh = connect(node)
        exec_cmd_return(ssh, "cd " + node["path"] + "/" + node["producer_name"] + " && ./stop.sh")


def remove_all_nodes(nodes):
    for node in nodes:
        ssh = connect(node)
        exec_cmd_return(ssh, "rm -rf " + node["path"] + "/" + node["producer_name"])


def status_all_nodes(nodes):
    for node in nodes:
        ssh = connect(node)
        lines = exec_cmd_return(ssh, "cd " + node["path"] + "/" + node["producer_name"] + " && ./status.sh")
        status = "%s:%s producer:%s status:%s" % (node["host"], node["port"], node["producer_name"], lines[0])
        print(status.encode("utf-8"))


def tar_nodes(nodes):
    for node in nodes:
        cmd = "tar czf " + deploy_path + "/" + node["producer_name"] + ".tar.gz " + deploy_path + "/" + node[
            "producer_name"]
        subprocess.check_output([cmd], shell=True)


def send_nodes(nodes):
    for node in nodes:
        root_path = node["path"] + "/"
        ssh = connect(node)
        exec_cmd(ssh, "mkdir -p " + root_path)
        upload(ssh, deploy_path + "/" + node["producer_name"] + ".tar.gz",
               root_path + node["producer_name"] + ".tar.gz")
        exec_cmd(ssh, "cd " + root_path + " && tar zxf " + root_path + node[
            "producer_name"] + ".tar.gz --strip-components 2")
        # cmd = "tar czf " + deploy_path + "/" + node["producer_name"] + ".tar.gz " + deploy_path + "/" + node[
        #     "producer_name"]
        # subprocess.check_output([cmd], shell=True)


# def send_nodes():

# def add_producer():
# cleos push action eosio setprods '{"schedule":[{"producer_name":"node2","block_signing_key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"},{"producer_name":"node3","block_signing_key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"},{"producer_name":"eosio","block_signing_key":"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"}]}' -p eosio

def parse(config_file):
    config = configparser.ConfigParser()
    config.read(config_file)
    print(config.sections())


def create_p2p_peers(nodes, node_name):
    p2p_peers = []
    for node in nodes:
        if node["producer_name"] != node_name:
            peer = node["host"] + ":" + node["p2p_port"]
            p2p_peers.append(peer)
    return p2p_peers


def generate_config_ini(file_path, http_server, p2p_server, p2p_peers, agent_name, produce_name, private_key,
                        public_key):
    config = "http-server-address = " + http_server + "\n"
    config += "p2p-listen-endpoint = " + p2p_server + "\n"
    for peer in p2p_peers:
        config += "p2p-peer-address = " + peer + "\n"
    config += "agent-name = \"" + agent_name + "\"\n"

    config += "enable-stale-production = true" + "\n"

    config += "producer-name = " + produce_name + "\n"
    config += "plugin = eosio::chain_api_plugin" + "\n"
    config += "plugin = eosio::history_api_plugin" + "\n"
    config += "plugin = eosio::chain_plugin" + "\n"
    config += "plugin = eosio::history_plugin" + "\n"
    config += "plugin = eosio::net_plugin" + "\n"
    config += "plugin = eosio::net_api_plugin" + "\n"
    config += "plugin = eosio::producer_api_plugin" + "\n"
    config += "private-key = [\"" + private_key + "\",\"" + public_key + "\"]" + "\n"

    # print(config)
    fp = open(file_path, "wb")
    fp.write(config.encode('utf-8'))


def create_node_configs(nodes):
    print("create node config & script")
    for node in nodes:
        path = deploy_path + "/" + node["producer_name"] + "/config"
        try:
            os.makedirs(path, 0o755)
        except os.error:
            pass
        p2p_peers = create_p2p_peers(nodes, node["producer_name"])
        http_server = "0.0.0.0:" + node["rpc_port"]
        p2p_server = "0.0.0.0:" + node["p2p_port"]
        generate_config_ini(path + "/config.ini", http_server, p2p_server, p2p_peers, node["agent_name"],
                            node["producer_name"], node["pubkey"], node["prikey"])
        if node["producer_name"] == "eosio":
            cmd = "cp ./script/start.sh " + deploy_path + "/" + node["producer_name"] + "/"
            subprocess.check_output([cmd], shell=True)
        else:
            cmd = "cp ./script/start_normal.sh " + deploy_path + "/" + node["producer_name"] + "/start.sh"
            subprocess.check_output([cmd], shell=True)
            cmd = "cp genesis.json " + deploy_path + "/" + node["producer_name"] + "/"
            subprocess.check_output([cmd], shell=True)

        cmd = "cp ./script/stop.sh " + deploy_path + "/" + node["producer_name"] + "/"
        subprocess.check_output([cmd], shell=True)

        cmd = "cp ./script/status.sh " + deploy_path + "/" + node["producer_name"] + "/"
        subprocess.check_output([cmd], shell=True)


def create_nodes(nodes):
    for node in nodes:
        path = deploy_path + "/" + node["producer_name"] + "/config"
        os.makedirs(path, 0o755)


def init_boot(node, nodes):
    print("init boot & start boot")
    start_boot(node)
    print("create system wallet & producer wallet")
    create_wallet()
    print("import wallet")
    import_wallet()
    print("create system account")
    create_system_account(nodes)
    print("fix update boot")
    fix_update()
    print("create system contract")
    create_system_contract()
    print("publish SYS token")
    publish_token()
    print("add producer")
    add_producer(nodes)
    print("stop boot")
    stop_boot(node)


def start_boot(node):
    config_path = deploy_path + "/" + node["producer_name"] + "/config"
    data_path = deploy_path + "/" + node["producer_name"]
    # cmd = "nodeos  --config-dir " + config_path + " --data-dir " + data_path + " >>" + data_path + "/nodeos.log 2>&1 &"
    cmd = "cd " + deploy_path + "/" + node["producer_name"] + "&& ./start.sh"
    subprocess.check_output([cmd], shell=True)


def stop_boot(node):
    cmd = "cd " + deploy_path + "/" + node["producer_name"] + " && ./stop.sh"
    subprocess.check_output([cmd], shell=True)


def create_wallet():
    cmd = "rm ~/eosio-wallet/press.wallet"
    try:
        subprocess.check_output([cmd], shell=True)
    except:
        pass

    cmd = "cleos wallet create -n press --to-console"
    keypair = subprocess.check_output([cmd], shell=True)
    lines = keypair.decode("utf-8").split("\n")
    print(lines)
    pw = lines[3].split("\"")[1]
    cmd = "cleos wallet  unlock -n press --password " + pw
    try:
        subprocess.check_output([cmd], shell=True)
    except:
        pass


def import_wallet():
    time.sleep(10)
    accounts = read_accounts()
    for account in accounts:
        cmd = "cleos wallet import -n press --private-key " + account["sk"]
        subprocess.check_output([cmd], shell=True)

    cmd = "cleos wallet import -n press --private-key " + "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
    subprocess.check_output([cmd], shell=True)


def create_system_account(nodes):
    pubkey = "EOS7fnisDs5quvRpqCWpPeWeUsBnJx6fAfeE4zobcBzHHBqE752h1"
    names = ["eosio.bpay", "eosio.msig", "eosio.names", "eosio.ram", "eosio.ramfee", "eosio.saving", "eosio.stake",
             "eosio.token", "eosio.vpay", "alice", "bob"]
    for n in names:
        print("create account:" + n)
        cmd = "cleos  create account eosio " + n + " " + pubkey
        subprocess.check_output([cmd], shell=True)
    for node in nodes:
        if node["producer_name"] != "eosio":
            cmd = "cleos  create account eosio " + node["producer_name"] + " " + pubkey
            subprocess.check_output([cmd], shell=True)


def fix_update():
    cmd = 'curl -v -X POST http://127.0.0.1:8888/v1/producer/schedule_protocol_feature_activations -d \'{"protocol_features_to_activate":["0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd"]}\''

    status = subprocess.check_output([cmd], shell=True)
    print(status)
    time.sleep(1)
    cmd = "cleos set contract eosio " + " ./eos/update/contracts/eosio.boot -p eosio@active"
    subprocess.check_output([cmd], shell=True)

    cmd = 'cleos -u http://127.0.0.1:8888 push transaction \'{"delay_sec":0,"max_cpu_usage_ms":0,"actions":[{"account":"eosio","name":"activate","data":{"feature_digest":"299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707"},"authorization":[{"actor":"eosio","permission":"active"}]}]}\''
    subprocess.check_output([cmd], shell=True)


def create_system_contract():
    print("create system contract")
    result = subprocess.check_output(["cleos set contract eosio ./eos/system/contracts/eosio.bios/"], shell=True)
    print(result)
    result = subprocess.check_output(
        ["cleos set contract eosio.token ./eos/system/contracts/eosio.token -p eosio.token"],
        shell=True)
    print(result)


def publish_token():
    subprocess.check_output(
        [
            'cleos push action eosio.token create  \'{"issuer":"eosio", "maximum_supply":"1000000000.0000 SYS"}\'  -p eosio.token'],
        shell=True)
    subprocess.check_output(
        ['cleos push action eosio.token issue  \'[ "eosio", "1000000000.0000 SYS", "memo" ]\'  -p eosio'], shell=True)
    # subprocess.check_output('cleos transfer eosio alice "25 SYS" 'transfer remark'')
    # cleos transfer alice bob '15 SYS' 'transfer remark'

    # cleos get currency balance eosio.token alice
    # cleos get currency balance eosio.token bob


def start(config_file):
    with open(config_file, "rb") as infile:
        nodes = json.load(infile)
        start_all_nodes(nodes)


def stop(config_file):
    with open(config_file, "rb") as infile:
        nodes = json.load(infile)
        stop_all_nodes(nodes)


def remove(config_file):
    with open(config_file, "rb") as infile:
        nodes = json.load(infile)
        remove_all_nodes(nodes)


def status(config_file):
    with open(config_file, "rb") as infile:
        nodes = json.load(infile)
        status_all_nodes(nodes)


if __name__ == '__main__':
    # status("./config.json")

    def cmd():
        return {
            "deploy": deploy,
            "start": start,
            "stop": stop,
            "remove": remove,
            "status": status,
        }


    if len(sys.argv) > 2:
        cmd()[sys.argv[1]](*sys.argv[2:])
    else:
        cmd()[sys.argv[1]]
    # deploy("./config.json")
    # generate_config_ini("", "127", "234", "456", "123", "eos", "abc", "efg")
    # start_producers()
